Completed
Push — master ( a87f83...46c1b5 )
by
unknown
01:39
created

sql.js ➔ ... ➔ ?!?.catch   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
nop 1
1
import {parse} from 'node-sqlparser'
2
import {Promise} from 'bluebird'
3
import path from 'path'
4
import {
5
  coreUtils,
6
  config,
7
  Manager,
8
  cmsData
9
} from '../../'
10
11
/**
12
 * take a string and json to escape sql character and convert to sql like syntax
13
 *
14
 * Example: escapeAbeValuesFromStringRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
15
 *
16
 * Return string: select title from ___abe_dot______abe_dot______abe___ where  `abe_meta.template`=`test`
17
 * 
18
 * 
19
 * @param  {String} str      raw abe request sql string
20
 * @param  {Object} jsonPage json object of post
21
 * @return {String}          escaped string
22
 */
23
export function escapeAbeValuesFromStringRequest(str, jsonPage) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonPage is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
24
  var matchFrom = /from .(.*?) /
25
  var matchVariable = /{{([a-zA-Z]*)}}/
26
27
  var matchFromExec = matchFrom.exec(str)
28
  if(matchFromExec != null && matchFromExec[1] != null) {
29
30
    var fromMatch
31
    var toReplace = matchFromExec[1]
32
    while (fromMatch = matchVariable.exec(toReplace)) {
33
      try {
34
        var value = eval('jsonPage.' + fromMatch[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
35
        if(value != null) {
36
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', value)
37
        }else {
38
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', '')
39
        }
40
      }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
41
      }
42
    }
43
44
    str = str.replace(matchFromExec[1], toReplace)
45
  }
46
47
  var from = /from ([\S\s]+)/.exec(str)
48
49
  var matches = from
50
  if(matches[1]) {
51
    var res = matches[1]
52
    var splitAttr = [' where ', ' order by ', ' limit ', ' WHERE ', ' ORDER BY ', ' LIMIT ']
53
    for(var i = 0; i < splitAttr.length; i++) {
54
      if(res.indexOf(splitAttr[i]) > -1) {
55
        res = res.substring(0, res.indexOf(splitAttr[i]))
56
      }
57
    }
58
    var escapedFrom = res.replace(/\//g, '___abe___')
59
    escapedFrom = escapedFrom.replace(/\./g, '___abe_dot___')
60
    escapedFrom = escapedFrom.replace(/-/g, '___abe_dash___')
61
    str = str.replace(res, escapedFrom)
62
  }
63
64
  str = str.replace(/``/g, '\'\'')
65
66
  return str
67
}
68
69
/**
70
 * analyse and create an object from request sql string
71
 *
72
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
73
 * 
74
 * @param  {String} str      Sql string request
75
 * @param  {Object} jsonPage json of post
76
 * @return {Object}          {type, columns, from, where, string, limit, orderby}
77
 */
78
export function handleSqlRequest(str, jsonPage) {
79
  var req = escapeAbeValuesFromStringRequest(str, jsonPage)
80
  var request = parse(req)
81
  var reconstructSql = ''
82
83
  // SQL TYPE
84
  var type = ''
85
  if(request.type != null) {
86
    type = request.type
87
  }
88
  reconstructSql += `${type} `
89
90
  // SQL COLUMNS
91
  var columns = []
92
  if(request.columns != null) {
93
    if(request.columns === '*') {
94
      columns.push('*')
95
    }else {
96
      Array.prototype.forEach.call(request.columns, (item) => {
97
        columns.push(item.expr.column)
98
      })
99
    }
100
  }
101
  reconstructSql += `${JSON.stringify(columns)} `
102
103
  // SQL FROM
104
  var from = []
105
  if(request.from != null) {
106
107
    Array.prototype.forEach.call(request.from, (item) => {
108
      from.push(item.table)
109
    })
110
  }else {
111
    from.push('*')
112
  }
113
  reconstructSql += `from ${JSON.stringify(from)} `
114
115
  var where = null
116
  if(request.where != null) {
117
    where = request.where
118
  }
119
120
  var limit = -1
121
  if(request.limit != null) {
122
    limit = request.limit[request.limit.length - 1].value
123
  }
124
125
  var orderby = null
126
  if(request.orderby != null && request.orderby.length > 0) {
127
    orderby = {
128
      column: request.orderby[0].expr.column,
129
      type: request.orderby[0].type
130
    }
131
    reconstructSql += `ORDER BY ${orderby.column} ${orderby.type} `
132
  }
133
134
  return {
135
    type: type,
136
    columns: columns,
137
    from: from,
138
    where: where,
139
    string: reconstructSql,
140
    limit: limit,
141
    orderby: orderby
142
  }
143
}
144
145
export function getDataSource(str) {
146
  var res = str.substring(str.indexOf('source=') + 8, str.length)
147
148
  var reg = /([^'"]*=[\s\S]*?}})/g
149
  var matches = res.match(reg)
150
  if(matches != null) {
151
    Array.prototype.forEach.call(matches, (match) => {
152
      res = res.replace(match, '')
153
    })
154
  }else {
155
    res = res.replace('}}', '')
156
  }
157
158
  return res.substring(0, res.length-1)
159
}
160
161
/**
162
 * replaces escaped characters with the right ones
163
 * @param  {String} statement the from clause
164
 * @return {String}           the from sanitized
165
 */
166
export function sanitizeFromStatement(statement){
167
  var from = ''
168
169
  if(statement != null) {
170
    from = statement[0].replace(/___abe_dot___/g, '.')
171
    from = from.replace(/___abe___/g, '/')
172
    from = from.replace(/___abe_dash___/g, '-')
173
  }
174
175
  return from
176
}
177
178
/**
179
 * calculate the directory to analyze from the from clause
180
 * @param  {String} statement the from clause
181
 * @param  {String} tplPath   the path from the template originator
182
 * @return {string}           the directory to analyze
183
 */
184
export function getFromDirectory(statement, tplPath){
185
  var pathFromDir = ''
186
  if(!tplPath){
187
    tplPath = '/'
188
  }
189
190
  if(statement === '' || statement === '*' || statement === '/') {
191
    pathFromDir = path.join(config.root, config.data.url)
192
  }else if(statement === './') {
193
    pathFromDir = path.join(config.root, config.data.url, tplPath)
194
  }else if(statement.indexOf('/') === 0) {
195
    pathFromDir = path.join(config.root, config.data.url, statement)
196
  }else if(statement.indexOf('/') !== 0) {
197
    pathFromDir = path.join(config.root, config.data.url, tplPath, statement)
198
  }
199
200
  return pathFromDir
201
}
202
203
/**
204
 * sort array of files from where clause
205
 *
206
 * @param  {Array} files
207
 * @param  {Object} orderby {orderby: {column: 'date'}} | {orderby: {column: 'random', type: 'ASC'}}
208
 * @return {Array}         sorted array
209
 */
210
export function executeOrderByClause(files, orderby){
211
  if(orderby != null) {
212
    if(orderby.column.toLowerCase() === 'random') {
213
      files = coreUtils.sort.shuffle(files)
214
    }else if(orderby.column.toLowerCase() === 'date') {
215
      if(orderby.type === 'ASC') {
216
        files.sort(coreUtils.sort.byDateAsc)
217
      }else if(orderby.type === 'DESC') {
218
        files.sort(coreUtils.sort.byDateDesc)
219
      }
220
    }
221
  }
222
223
  return files
224
}
225
226
/**
227
 * Keep only published post
228
 *
229
 * keepOnlyPublishedPost([files])
230
 *
231
 * @param  {Array} files      paths
232
 * @return {Array}                files
233
 */
234
export function keepOnlyPublishedPost(files){
235
  var publishedValue = []
236
  Array.prototype.forEach.call(files, (file) => {
237
    if (file.publish != null) {
238
      publishedValue.push(file.publish)
239
    }
240
  })
241
242
  return publishedValue
243
}
244
245
/**
246
 * Check array of files have path that match path statement
247
 *
248
 * executeFromClause([array], ['/'], ['/'])
249
 *
250
 * @param  {Array} statement      paths
251
 * @param  {Array} pathFromClause paths
252
 * @return {Array}                files
253
 */
254
export function executeFromClause(files, statement, pathFromClause){
255
  var from = sanitizeFromStatement(statement)
256
257
  // if the from clause ends with a dot, we won't recurse the directory analyze
258
  if(from.slice(-1) === '.'){
259
    from = from.slice(0, -1)
260
  }
261
  
262
  var fromDirectory = getFromDirectory(from, pathFromClause)
263
264
  var files_array = files.filter((element) => {
265
    if (element.path.indexOf(fromDirectory) > -1) {
266
      return true
267
    }
268
    return false
269
  })
270
271
  return files_array
272
}
273
274
/**
275
 * Execute sql query like to find abe json post that match the query
276
 * 
277
 * @param  {Array} pathQuery of paths
278
 * @param  {String} match     request sql
279
 * @param  {Object} jsonPage  json of post
280
 * @return {Array}           found object that match
281
 */
282
export function execQuery(pathQuery, match, jsonPage) {
283
  var files = keepOnlyPublishedPost(Manager.instance.getList())
284
  var request = handleSqlRequest(cmsData.regex.getAttr(match, 'source'), jsonPage)
285
286
  files = executeFromClause(files, request.from, pathQuery)
287
  files = executeWhereClause(files, request.where, request.limit, request.columns, jsonPage)
288
  files = executeOrderByClause(files, request.orderby)
289
  return files
290
}
291
292
export function executeQuerySync(pathQuerySync, match, jsonPage) {
293
  return execQuery(pathQuerySync, match, jsonPage)
294
}
295
296
export function executeQuery(pathexecuteQuery, match, jsonPage) {
297
  var p = new Promise((resolve) => {
298
    var res = execQuery(pathexecuteQuery, match, jsonPage)
299
    resolve(res)
300
  }).catch(function(e) {
301
    console.error(e)
302
  })
303
304
  return p
305
}
306
307
/**
308
 * check if a given string an url, string json, file url, abe sql request
309
 * 
310
 * get('http://google.com')
311
 * get('{"test":"test"}')
312
 * get('select * from ../')
313
 * get('test')
314
 * 
315
 * @param  {String} str 
316
 * @return {String} url | request | value | file | other
317
 */
318
export function getSourceType(str) {
319
  if(/http:\/\/|https:\/\//.test(str)) {
320
    return 'url'
321
  }
322
323
  if(/select[\S\s]*?from/.test(str)) {
324
    return 'request'
325
  }
326
327
  try {
328
    JSON.parse(str)
329
    return 'value'
330
  }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
331
332
  }
333
334
  if(/\.json/.test(str)) {
335
    return 'file'
336
  }
337
338
  return 'other'
339
}
340
341
/**
342
 * return array of post that match sql where statement
343
 *
344
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`article`', {})
345
 *
346
 * @param  {Array} files    
347
 * @param  {Object} wheres   clause
348
 * @param  {Int} maxLimit 
349
 * @param  {Array} columns  sql
350
 * @param  {Object} jsonPage json post
351
 * @return {Array}          of files
352
 */
353
export function executeWhereClause(files, wheres, maxLimit, columns, jsonPage){
354
  if(typeof wheres === 'undefined' || wheres === null) return files
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
355
  var res = []
356
  var limit = 0
357
  var json = {}
358
  var jsonValues = {}
359
360
  for(let file of files) {
361
    if(limit < maxLimit || maxLimit === -1) {
362
      if(wheres != null) {
363
        if(!recurseWhere(wheres, file, jsonPage)) {
364
          json = JSON.parse(JSON.stringify(file))
365
          jsonValues = {}
366
367
          if(columns != null && columns.length > 0 && columns[0] !== '*') {
368
            Array.prototype.forEach.call(columns, (column) => {
369
              if(json[column] != null) {
0 ignored issues
show
Bug introduced by
The variable json is changed as part of the for-each loop for example by JSON.parse(JSON.stringify(file)) on line 364. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
370
                jsonValues[column] = json[column]
0 ignored issues
show
Bug introduced by
The variable jsonValues is changed as part of the for-each loop for example by {} on line 365. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
371
              }
372
            })
373
            jsonValues['abe_meta'] = json['abe_meta']
374
          }else {
375
            jsonValues = json
376
          }
377
378
          res.push(jsonValues)
379
          limit++
380
        }
381
      }
382
    } else {
383
      break
384
    }
385
  }
386
387
  return res
388
}
389
390
/**
391
 * Compare where left and where right clause
392
 * 
393
 * @param  {Object} where           clause
394
 * @param  {Object} jsonDoc         json of current post
395
 * @param  {Object} jsonOriginalDoc json of post to compare
396
 * @return {Object}                 {left: value, right: value}
397
 */
398
export function getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonOriginalDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter jsonDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
399
  var regexIsVariable = /^{{(.*)}}$/
400
  var value = null
401
  var compare = null
402
403
  try {
404
    var variableLeft = where.left.column
405
    var checkIfLeftIsAVariable = regexIsVariable.exec(variableLeft)
406
    if(checkIfLeftIsAVariable != null && checkIfLeftIsAVariable.length > 0) {
407
      variableLeft = checkIfLeftIsAVariable[1]
408
    }
409
    value = eval('jsonDoc.' + variableLeft)
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
410
  }catch(e) {
411
    // console.log('e', e)
412
  }
413
  
414
  if(where.operator === 'IN' || where.operator === 'NOT IN') {
415
    compare = []
416
    Array.prototype.forEach.call(where.right.value, (right) => {
417
      var matchRightVariable = regexIsVariable.exec(right.column)
418
      if(matchRightVariable != null && matchRightVariable.length > 0) {
419
        try {
420
          var jsonOriginalValues = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
421
          Array.prototype.forEach.call(jsonOriginalValues, (jsonOriginalValue) => {
0 ignored issues
show
Unused Code introduced by
The parameter jsonOriginalValue is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
422
            compare.push(eval('jsonOriginalValue.' + where.left.column))
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
423
          })
424
        }catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
425
      }
426
      else{
427
        compare.push(right.column)
428
      }
429
    })
430
  } else {
431
    if(where.right.column != null) {
432
      compare = where.right.column
433
    } else if(where.right.value != null) {
434
      compare = where.right.value
435
    }
436
437
    var matchRightVariable = regexIsVariable.exec(compare)
438
439
    if(matchRightVariable != null && matchRightVariable.length > 0) {
440
      try {
441
        var shouldCompare = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
442
        if(shouldCompare != null) {
443
          compare = shouldCompare
444
        }else {
445
          compare = null
446
        }
447
      }catch(e) {
448
        compare = null
449
      }
450
    }
451
  }
452
453
  return {
454
    left: value,
455
    right: compare
456
  }
457
}
458
459
/**
460
 * Check where.left value that match where operator (=, !=, >, >=, <, <=, LIKE, NOT LIKE, AND, OR, IN, NOT IN)
461
 * if operator AND or OR
462
 * Recurse on where.left and where.right sql clause
463
 *
464
 * 
465
 * @param  {Object} where           clause
466
 * @param  {Object} jsonDoc         json of current post
467
 * @param  {Object} jsonOriginalDoc json of post to compare
468
 * @return {Boolean}                 true if not matching | false if matching
469
 */
470
export function recurseWhere(where, jsonDoc, jsonOriginalDoc) {
471
  var isNotLeftCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotLeftCorrect seems to be never used. Consider removing it.
Loading history...
472
  var isNotRightCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotRightCorrect seems to be never used. Consider removing it.
Loading history...
473
  var isNotCorrect = false
474
  var values
475
476
  switch(where.operator) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
477
  case '=':
478
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
479
    isNotCorrect = !(values.left === values.right)
480
    break
481
  case '!=':
482
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
483
    isNotCorrect = !(values.left !== values.right)
484
    break
485
  case '>':
486
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
487
    isNotCorrect = !(values.left > values.right)
488
    break
489
  case '>=':
490
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
491
    isNotCorrect = !(values.left >= values.right)
492
    break
493
  case '<':
494
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
495
    isNotCorrect = !(values.left < values.right)
496
    break
497
  case '<=':
498
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
499
    isNotCorrect = !(values.left <= values.right)
500
    break
501
  case 'LIKE':
502
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
503
    isNotCorrect = !(values.left && values.left.indexOf(values.right) > -1)
504
    break
505
  case 'NOT LIKE':
506
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
507
    isNotCorrect = !(values.left && values.left.indexOf(values.right) === -1)
508
    break
509
  case 'AND':
510
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
511
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
512
    isNotCorrect = (isNotLeftCorrect || isNotRightCorrect) ? true : false
513
    break
514
  case 'OR':
515
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
516
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
517
    isNotCorrect = (isNotLeftCorrect && isNotRightCorrect) ? true : false
518
    break
519
  case 'IN':
520
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
521
    isNotCorrect = true
522
    Array.prototype.forEach.call(values.right, (right) => {
523
      if(values.left === right) {
524
        isNotCorrect = false
525
      }
526
    })
527
    break
528
  case 'NOT IN':
529
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
530
    isNotCorrect = false
531
    Array.prototype.forEach.call(values.right, (right) => {
532
      if(values.left === right) {
533
        isNotCorrect = true
534
      }
535
    })
536
    break
537
  }
538
  return isNotCorrect
539
}